﻿#region License
//-----------------------------------------------------------------------------
// Copyright (c) 2008, Aaron MacDougall, Daniel Jeffery
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// * Neither the name of Aaron MacDougall or Daniel Jeffery nor the names of its contributors may
//   be used to endorse or promote products derived from this software without
//   specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using System.Collections;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System.Diagnostics;
#endregion

namespace Agro2D
{
    /// <summary>
    /// Enum describes the transition of a state.
    /// </summary>
    public enum StateTransitions
    {
        TransitionOn,
        Active,
        TransitionOff,
        Hidden,
    }

    /// <summary>
    /// Base class to help with State transition effects.
    /// </summary>
    public abstract class IStateTransition
    {
        #region Fields
        protected TimeSpan transitionOnTime = TimeSpan.Zero;
        protected TimeSpan transitionOffTime = TimeSpan.Zero;
        protected float transitionPosition = 1;
        protected StateTransitions stateTransition = StateTransitions.TransitionOn;
        protected bool otherStateHasFocus;
        protected bool isExiting = false;
        protected State parentState;
        #endregion

        #region Properties
        /// <summary>
        /// Indicates how long the State takes to
        /// transition on when it is activated.
        /// </summary>
        public TimeSpan TransitionOnTime
        {
            get { return transitionOnTime; }
            protected set { transitionOnTime = value; }
        }

        /// <summary>
        /// Indicates how long the State takes to
        /// transition off when it is deactivated.
        /// </summary>
        public TimeSpan TransitionOffTime
        {
            get { return transitionOffTime; }
            protected set { transitionOffTime = value; }
        }

        /// <summary>
        /// Gets the current position of the State transition, ranging
        /// from zero (fully active) to one (inactive).
        /// </summary>
        public float TransitionTime
        {
            get { return transitionPosition; }
            protected set { transitionPosition = value; }
        }

        /// <summary>
        /// Gets the current alpha of the State transition, ranging
        /// from 255 (fully active, no transition) to 0 (transitioned
        /// fully off to nothing).
        /// </summary>
        public byte TransitionAlpha
        {
            get { return (byte)(255 - TransitionTime * 255); }
        }

        /// <summary>
        /// Gets the current transition state.
        /// </summary>
        public StateTransitions StateTransiton
        {
            get { return stateTransition; }
            protected set { stateTransition = value; }
        }

        /// <summary>
        /// This property indicates whether the State is exiting:
        /// if set, the State will automatically remove itself as soon as the
        /// transition finishes.
        /// </summary>
        public bool IsExiting
        {
            get { return isExiting; }
            protected internal set { isExiting = value; }
        }
        #endregion

        #region Constructors/Destructors
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="parentState">Parent State for which the transition applies to</param>
        public IStateTransition(State parentState)
        {
            this.parentState = parentState;
        }
        #endregion

        #region Update and Draw

        /// <summary>
        /// Allows the State to run logic, such as updating the transition time.
        /// Unlike HandleInput, this method is called regardless of whether the State
        /// is active, hidden, or in the middle of a transition.
        /// </summary>
        public virtual void UpdateTransition(GameTime gameTime, bool otherStateHasFocus,
                                                      bool coveredByOtherState)
        {
            this.otherStateHasFocus = otherStateHasFocus;

            if(isExiting)
            {
                // If the State is going away to die, it should transition off.
                stateTransition = StateTransitions.TransitionOff;

                if(!UpdateTransition(gameTime, transitionOffTime, 1))
                {
                    // When the transition finishes, remove the State.
                    parentState.StateManager.RemoveState(parentState);
                }
            }
            else if(coveredByOtherState)
            {
                // If the State is covered by another, it should transition off.
                if(UpdateTransition(gameTime, transitionOffTime, 1))
                {
                    // Still busy transitioning.
                    stateTransition = StateTransitions.TransitionOff;
                }
                else
                {
                    // Transition finished!
                    stateTransition = StateTransitions.Hidden;
                }
            }
            else
            {
                // Otherwise the State should transition on and become active.
                if(UpdateTransition(gameTime, transitionOnTime, -1))
                {
                    // Still busy transitioning.
                    stateTransition = StateTransitions.TransitionOn;
                }
                else
                {
                    // Transition finished!
                    stateTransition = StateTransitions.Active;
                }
            }

        }

        /// <summary>
        /// Helper for updating the State transition position.
        /// </summary>
        bool UpdateTransition(GameTime gameTime, TimeSpan time, int direction)
        {
            // How much should we move by?
            float transitionDelta;

            if(time == TimeSpan.Zero)
                transitionDelta = 1;
            else
                transitionDelta = (float)(gameTime.ElapsedGameTime.TotalMilliseconds /
                                          time.TotalMilliseconds);

            // Update the transition position.
            transitionPosition += transitionDelta * direction;

            // Did we reach the end of the transition?
            if((transitionPosition <= 0) || (transitionPosition >= 1))
            {
                transitionPosition = MathHelper.Clamp(transitionPosition, 0, 1);
                return false;
            }

            // Otherwise we are still busy transitioning.
            return true;
        }

        /// <summary>
        /// Tells the State to go away. Unlike StateManager.RemoveState, which
        /// instantly kills the State, this method respects the transition timings
        /// and will give the State a chance to gradually transition off.
        /// </summary>
        public void ExitState()
        {
            if(TransitionOffTime == TimeSpan.Zero)
            {
                // If the State has a zero transition time, remove it immediately.
                parentState.StateManager.RemoveState(parentState);
            }
            else
            {
                // Otherwise flag that it should transition off and then exit.
                isExiting = true;
            }
        }
        #endregion
    }

    /// <summary>
    /// A State is a single 'layer' that has update and draw logic, and which
    /// can be combined with other 'layers'. Examples of a state are the main 
    /// game itself as well as menu screens.
    /// </summary>
    public abstract class State
    {
        #region Fields
        private bool isActive = false;
        private bool isPopup = false;
        private StateManager stateManager;
        #endregion

        #region Properties
        /// <summary>
        /// Indicates whether the current state is given drawing priority
        /// over currently drawing states
        /// </summary>
        public bool IsPopup
        {
            get { return isPopup; }
            set { isPopup = value; }
        }

        /// <summary>
        /// Checks whether this State is active.
        /// </summary>
        public bool IsActive
        {
            get { return isActive; }
            set { isActive = value; }
        }

        /// <summary>
        /// Gets the manager that this State belongs to.
        /// </summary>
        public StateManager StateManager
        {
            get { return stateManager; }
            internal set { stateManager = value; }
        }
        #endregion

        #region Initialization
        /// <summary>
        /// Load graphics content for the State.
        /// </summary>
        public virtual void LoadContent() { }

        /// <summary>
        /// Unload content for the State.
        /// </summary>
        public virtual void UnloadContent() { }
        #endregion

        #region Update and Draw
        /// <summary>
        /// Allows the State to run logic
        /// </summary>
        public virtual void Update(GameTime gameTime)
        {
            
        }

        /// <summary>
        /// Allows the State to handle user input. Unlike Update, this method
        /// is only called when the State is active, and not when some other
        /// State has taken the focus.
        /// </summary>
        public virtual void HandleInput() { }

        /// <summary>
        /// This is called when the State should draw itself.
        /// </summary>
        public virtual void Draw(GameTime gameTime) { }
        #endregion

        #region Public Methods
        
        #endregion
    }

    /// <summary>
    /// The State manager is a component which manages one or more State
    /// instances. It maintains a stack of States, calls their Update and Draw
    /// methods at the appropriate times.
    /// </summary>
    public class StateManager: DrawableGameComponent
    {
        #region Fields
        private List<State> states = new List<State>();
        private List<State> statesToUpdate = new List<State>();

        private SpriteBatch spriteBatch;
        private TextureReference blankTexture;

        private bool isInitialized;
        private bool traceEnabled = false;
        private bool isGamePaused;
        #endregion

        #region Properties
        /// <summary>
        /// A default SpriteBatch shared by all the States. This saves
        /// each State having to bother creating their own local instance.
        /// </summary>
        public SpriteBatch SpriteBatch
        {
            get { return spriteBatch; }
        }

        /// <summary>
        /// If true, the manager prints out a list of all the States
        /// each time it is updated. This can be useful for making sure
        /// everything is being added and removed at the right times.
        /// </summary>
        public bool TraceEnabled
        {
            get { return traceEnabled; }
            set { traceEnabled = value; }
        }

        #endregion

        #region Initialization
        /// <summary>
        /// Constructs a new State manager component.
        /// </summary>
        public StateManager(Game game)
            : base(game)
        {
        }

        /// <summary>
        /// Initializes the State manager component.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();

            isInitialized = true;
        }

        /// <summary>
        /// Load your graphics content.
        /// </summary>
        protected override void LoadContent()
        {
            // Load content belonging to the State manager.
            ContentManager content = Game.Content;

            spriteBatch = new SpriteBatch(GraphicsDevice);
            blankTexture = TextureManager.Instance.CreateTexture("Content/Blank");

            // Tell each of the States to load their content.
            foreach (State State in states)
            {
                State.LoadContent();
            }
        }

        /// <summary>
        /// Unload your graphics content.
        /// </summary>
        protected override void UnloadContent()
        {
            // Tell each of the States to unload their content.
            //foreach (State State in states)
            //{
            //    State.UnloadContent();
            //}
        }
        #endregion

        #region Update and Draw
        /// <summary>
        /// Allows each State to run logic.
        /// </summary>
        State State;
        public override void Update(GameTime gameTime)
        {
            // Make a copy of the master State list, to avoid confusion if
            // the process of updating one State adds or removes others.
            statesToUpdate.Clear();

            foreach (State State in states)
                statesToUpdate.Add(State);

            // Loop as long as there are States waiting to be updated.
            while (statesToUpdate.Count > 0)
            {
                // Pop the topmost State off the waiting list.
                State = statesToUpdate[statesToUpdate.Count - 1];

                statesToUpdate.RemoveAt(statesToUpdate.Count - 1);

                // Update the State.
                State.Update(gameTime);                
                State.HandleInput();
            }

            // Print debug trace.
            if (traceEnabled)
                TraceStates();
        }

        /// <summary>
        /// Prints a list of all the States, for debugging.
        /// </summary>
        void TraceStates()
        {
            List<string> StateNames = new List<string>();

            foreach (State state in states)
                StateNames.Add(state.GetType().Name);

            Trace.WriteLine(string.Join(", ", StateNames.ToArray()));
        }        

        /// <summary>
        /// Tells each State to draw itself.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            foreach (State State in states)
            {
                State.Draw(gameTime);
            }
        }


        #endregion

        #region Public Methods
        /// <summary>
        /// Adds a new State to the State manager.
        /// </summary>
        public void AddState(State state)
        {
            state.StateManager = this;

            //Debug.Assert(isInitialized, "StateManager has not been added to Game.Components");
            // If we have a graphics device, tell the State to load content.
            if (isInitialized)
            {
                state.LoadContent();
            }

            states.Add(state);
        }

        /// <summary>
        /// Global value that can be used to pause execution of all states 
        /// if they comply. Make sure you handle input to unpause the game
        /// </summary>
        public bool GamePaused
        {
            get { return isGamePaused; }
            set { isGamePaused = value; }
        }

        /// <summary>
        /// Removes all current states and closes the application.
        /// </summary>
        public void CloseApp()
        {
            //foreach(State state in states)
                //RemoveState(state);
#if WINDOWS
            Environment.Exit(0);
#endif
        }
        
        /// <summary>
        /// Removes a State from the State manager. You should normally
        /// use State.ExitState instead of calling this directly, so
        /// the State can gradually transition off rather than just being
        /// instantly removed.
        /// </summary>
        public void RemoveState(State State)
        {
            Debug.Assert(isInitialized, "StateManager has not been added to Game.Components");
            // If we have a graphics device, tell the State to unload content.
            if (isInitialized)
            {
                State.UnloadContent();
            }

            states.Remove(State);
            statesToUpdate.Remove(State);
        }


        /// <summary>
        /// Expose an array holding all the States. We return a copy rather
        /// than the real master list, because States should only ever be added
        /// or removed using the AddState and RemoveState methods.
        /// </summary>
        public State[] GetStates()
        {
            return states.ToArray();
        }

        /// <summary>
        /// Helper draws a translucent black fullState sprite, used for fading
        /// States in and out, and for darkening the background behind popups.
        /// </summary>
        public void FadeBackBufferToBlack(int alpha)
        {
            Viewport viewport = GraphicsDevice.Viewport;

            spriteBatch.Begin();

            spriteBatch.Draw(TextureManager.Instance.GetTexture(blankTexture),
                             new Rectangle(0, 0, viewport.Width, viewport.Height),
                             new Color(0, 0, 0, (byte)alpha));

            spriteBatch.End();
        }
        #endregion

    }
}
